/*
 * COPYRIGHT. ShenZhen JiMi Technology Co., Ltd. 2017.
 * ALL RIGHTS RESERVED.
 *
 * No part of this publication may be reproduced, stored in a retrieval system, or transmitted,
 * on any form or by any means, electronic, mechanical, photocopying, recording, 
 * or otherwise, without the prior written permission of ShenZhen JiMi Network Technology Co., Ltd.
 *
 * Amendment History:
 * 
 * Date                   By              Description
 * -------------------    -----------     -------------------------------------------
 * 2017年4月19日    li.shangzhi         Create the class
 * http://www.jimilab.com/
 */

package com.jimi.web.util;

import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.jimi.framework.context.SpringContextHolder;
import com.jimi.tracker.client.ResponseHandler;
import com.jimi.tracker.client.RouteClient;
import com.jimi.tracker.client.RouteClientPool;
import com.jimi.tracker.client.RouteGlobal;
import com.jimi.tracker.trans.route.pool.ClientPool;
import com.jimi.utils.MessageSourceUtil;
import com.jimi.web.model.ElectricFence;

/**
 * @FileName CmdUtil.java
 * @Description:
 *
 * @Date 2017年4月19日 下午2:06:37
 * @author li.shangzhi
 * @version 1.0
 */
public class CmdUtil {

	protected static final Logger logger = LoggerFactory.getLogger(CmdUtil.class);

	private static RedisTemplate<String, String> redisTemplate = SpringContextHolder.getBean("tuqiangRedisTemplate");
	
	/**
	 * 路由客户端连接池
	 */
	public static ClientPool<RouteClient> routePool = RouteClientPool.getPool();

	/**
	 * @Title: sendInstruct
	 * @Description: 发送围栏指令
	 * @param fence
	 *            围栏属性对象
	 * @param fenceType
	 *            围栏类型，单围栏、多围栏；1：单围栏、N：多围栏
	 * @param alarmSwitch
	 *            围栏报警开关，ON/OFF
	 * @param instructType
	 *            指令类型，ON/OFF
	 * @return
	 * @author li.shangzhi
	 * @return
	 * @date 2017年4月19日 下午3:51:16
	 */
	public static CmdAPIContent sendInstruct(ElectricFence electricFence, String fenceType, String alarmSwitch, String instructType) {
		String commandStr = getElectricFenceCommand(electricFence, fenceType, alarmSwitch, instructType);
		logger.info("发送围栏指令：{}", commandStr);
		int cmdSqNo = Integer.valueOf(redisTemplate.boundValueOps(RedisGlobals.CmdSqNo).increment(1).toString());
		CmdAPIContent ctx = CmdUtil.sendSmsCmd(electricFence.getImei(), commandStr, cmdSqNo);
		logger.info("围栏指令返回：" + JSON.toJSONString(ctx));
		return ctx;
	}

	/**
	 * @Title: getElectricFenceCommand
	 * @Description: 围栏指令拼接
	 * @param fence
	 *            围栏属性对象
	 * @param fenceType
	 *            围栏类型，单围栏、多围栏；1：单围栏、N：多围栏
	 * @param alarmSwitch
	 *            围栏报警开关，ON/OFF
	 * @param instructType
	 *            指令类型，ON/OFF
	 * @return
	 * @author li.shangzhi
	 * @date 2017年4月19日 下午3:51:16
	 */
	public static String getElectricFenceCommand(ElectricFence fence, String fenceType, String alarmSwitch, String instructType) {
		String cmd = "";
		String FENCE = "FENCE"; // （单围栏指令）
		String GFENCE = "GFENCE"; // （多围栏指令）
		String B = "OFF"; // B=ON/OFF；开启/关闭围栏报警；默认值为：关闭
							// (点击“进”“出”其中任意一个为开启，不点为关闭)
		if ("ON".equals(alarmSwitch)) {
			B = "ON";
		}
		String p[] = fence.getGpsCoordinate().split(",");
		String D = p[1]; // D=圆心纬度
		String E = p[0]; // E=圆心经度
		String F = fence.getRadius();// F=1～9999；围栏半径，单位：百米
		String X = ""; // X=IN/OUT；IN：入围栏报警，OUT：出围栏报警，为空：进/出围栏报警；默认为进出围栏都报警
		if (fence.getAlarmType().equals("in")) {
			X = "IN";
		} else if (fence.getAlarmType().equals("out")) {
			X = "OUT";
		} else {
			X = "";
		}
		String M = fence.getReportMode(); // 报警上报方式，0：仅GPRS，1：SMS+GPRS，默认为：1
		String N = fence.getInstructNo(); // N=1-5;电子围栏序号；（多围栏指令）
		if (instructType.equals("OFF")) { // 删除指令
			if (fenceType.equals("1")) {
				cmd = FENCE + "," + "OFF#";
			} else if (fenceType.equals("N")) {
				cmd = GFENCE + "," + N + ",OFF#";
			}
		} else if (instructType.equals("ON")) {// 增加指令
			if (fenceType.equals("1")) {
				cmd = FENCE + "," + B + ",0," + D + "," + E + "," + F + "," + X + "," + M + "#";
			} else if (fenceType.equals("N")) {
				cmd = GFENCE + "," + N + "," + B + ",0," + D + "," + E + "," + F + "," + X + "," + M + "#";
			}
		}
		return cmd;
	}

	/**
	 * 
	 * @param imei
	 * @param funNo
	 * @param funNo
	 *            * 参见CmdFunNo,不同机型功能号不同，同一机型可能有多个功能号如：V50有两种指令 0x80 0x8f
	 * @param cmd
	 *            指令字符串
	 * @param serviceFlagId
	 *            服务标识
	 */
	public static CmdAPIContent sendSmsCmd(String imei, String cmd, int serviceFlagId) {
		CmdAPIContent result = new CmdAPIContent();
		// 构造参数
		Map<String, Object> map = new HashMap<>();
		map.put(RouteGlobal.InnerField.serverFlagId, serviceFlagId);// 服务器标志ID
		map.put(RouteGlobal.InnerField.cmdContent, cmd);// 指令内容
		map.put(RouteGlobal.InnerField.language, RouteGlobal.Language.zh);// zh语言标识

		String mcType = getMcType(imei);
		if (StringUtils.isNoneBlank(mcType)) {
			Integer proNo = getSmsProNoByMcType(mcType);
			result = sendCommand(imei, proNo, map, true);
		} else {
			result.setCode(Globals.DeviceToCode.CommandsNot_support);
			result.setMsg("该设备型号不支持");
			logger.error("设备{}没有配置机型，不能找到引机型的协议号", imei);
		}
		return result;
	}

	/**
	 * @Title: sendNormalInstructionCmd
	 * @Description: 发送常规指令
	 * @param imei
	 *            接收设备imei
	 * @param cmd
	 *            发送的命令
	 * @param proNo
	 *            序号
	 * @param sync
	 *            是否同步 true-同步 false-异步
	 * @return
	 * @author li.shangzhi
	 * @date 2017年7月25日 下午4:56:07
	 */
	public static CmdAPIContent sendNormalInstructionCmd(String imei, String cmd, Integer proNo, boolean sync) {
		// 构造参数
		Map<String, Object> map = new HashMap<>();
		map.put(RouteGlobal.InnerField.serverFlagId, 0);// 服务器标志ID
		map.put(RouteGlobal.InnerField.cmdContent, cmd);// 指令内容
		map.put(RouteGlobal.InnerField.language, RouteGlobal.Language.zh);// zh语言标识
		map.put("_cmdType", Globals.InstructionCode.NORMAL_INS);
		logger.info("设备{}下发的指令为：{}", imei, cmd);
		return sendCommand(imei, proNo, map, sync);
	}

	/**
	 * @Title: sendCommand
	 * @Description: 发送命令
	 * @param imei
	 *            接收的设备imei
	 * @param proNo
	 *            协议Code，十进制
	 * @param map
	 *            发送内容
	 * @param sync
	 *            是否同步 true-同步 false-异步
	 * @return
	 * @author li.shangzhi
	 * @date 2017年7月25日 下午5:27:10
	 */
	private static CmdAPIContent sendCommand(String imei, Integer proNo, Map<String, Object> map, boolean sync) {
		CmdAPIContent result = new CmdAPIContent();
		// 发送指令
		RouteClient client = null;
		try {
			if (null == routePool) {
				routePool = RouteClientPool.getPool();
			}
			client = routePool.borrowObject();
			if (null != proNo) {
				if (sync) {
					Map<String, String> cmdRet = client.sendCmdSync(imei, proNo, map, Globals.InstructionCode.SENDCOMMANDTIME,
							TimeUnit.SECONDS);
					// 结果转码
					int code = deviceToCode(cmdRet);
					result.setCode(code);
					result.setMsg(cmdRet.get("_msg"));
					result.setData(cmdRet.get("_content"));
					result.setCmdSeqNo(cmdRet.get("_serverFlagId"));
					// 错误码，结果转换
				} else {
					client.sendCmdAsync(imei, proNo, map, new ResponseHandler() {
						@Override
						public void onMsg(String paramString, Map<String, String> paramMap) throws InterruptedException {

						}
					});
					result.setCode(0);
					result.setMsg("发送成功");
				}
			} else {
				logger.error("设备{}不支持在线指令。", imei);
				result.setCode(Globals.DeviceToCode.CommandsNot_support);
				result.setMsg("不支持");
			}
		} catch (Exception e) {
			result.setCode(Globals.DeviceToCode.CommandsCommand_was_not_executed_correctly);
			result.setMsg("异常");
			logger.error("设备{}发送指令异常。发送内容：{}", imei, JSONObject.toJSONString(map), e);
		} finally {
			if (null != client) {
				routePool.returnObject(client);
			}
		}
		return result;
	}

	/**
	 * 获取设备机型
	 * 
	 * @param imei
	 * @return
	 */
	public static String getMcType(String imei) {
		BoundHashOperations<String, String, String> boundHashOperations = redisTemplate.boundHashOps(RedisGlobals.DC_IMEI_APPID);
		String json = boundHashOperations.get(imei);
		if (StringUtils.isNoneBlank(json)) {
			Map<String, String> map = JSON.parseObject(json, new TypeReference<Map<String, String>>() {
			});
			String mcType = map.get("mcType");
			return mcType;
		}
		return null;
	}

	/**
	 * 根据mcType从redis获取机型在线短消息指令协议号
	 * 
	 * @param mcType
	 * @return
	 */
	private static Integer getSmsProNoByMcType(String mcType) {
		BoundHashOperations<String, String, String> boundHashOperations = redisTemplate
				.boundHashOps(RedisGlobals.TRACKER_SMS_PRO_NO_Map_PRO);
		String smsProNoStr = boundHashOperations.get(mcType);
		Integer smsProNo = 0x80;
		try {
			if (StringUtils.isNotBlank(smsProNoStr)) {
				smsProNoStr = smsProNoStr.toLowerCase();
				if (smsProNoStr.charAt(0) == '0' && smsProNoStr.charAt(1) == 'x') {
					smsProNoStr = smsProNoStr.substring(2);
				}
				smsProNo = Integer.parseInt(smsProNoStr, 16);
			}
		} catch (NumberFormatException e) {
			logger.error("获取机型在线短消息指令协议号错误 ", e);
		}
		return smsProNo;
	}

	/**
	 * 结果码转换
	 * 
	 * @param result
	 * @param cmdRet
	 */
	public static int deviceToCode(Map<String, String> cmdRet) {
		int code = Globals.DeviceToCode.CommandsUnknown_error;
		// 错误码，结果转换
		String resultCode = cmdRet.get(RouteGlobal.InnerField.CmdResponseCode);
		switch (resultCode) {
		// 指令成功返回——指令内容可能是失败
		case RouteGlobal.ResponseCode.DEVICE_OK: {
			code = Globals.DeviceToCode.Command_execution_success;
			String content = cmdRet.get("_content");
			if (StringUtils.isNoneBlank(content)) {
				if (content.toLowerCase().contains("busy")) {
					code = Globals.DeviceToCode.CommandsDevice_Busy;
				}
			}
			break;
		}
		// 设备没在线(网关)
		case RouteGlobal.ResponseCode.DEVICE_OFF_LINE: {
			logger.error(cmdRet.get(RouteGlobal.InnerField.CmdResponseMsg));
			code = Globals.DeviceToCode.CommandsDevice_NONET;
			break;
		}
		// 设备没有注册到路由表（Redis中没有找到IMEI和网关ID映射）
		case RouteGlobal.ResponseCode.DEVICE_OFF_ROUTE: {
			logger.error(cmdRet.get(RouteGlobal.InnerField.CmdResponseMsg));
			code = Globals.DeviceToCode.CommandsDevice_NONET;
			break;
		}
		// 参数无效
		case RouteGlobal.ResponseCode.INVALID_PARAMS: {
			logger.error(cmdRet.get(RouteGlobal.InnerField.CmdResponseMsg));
			code = Globals.DeviceToCode.CommandsParameter_error;
			break;
		}
		// 网络错误(连接断开，等)
		case RouteGlobal.ResponseCode.NET_ERROR: {
			logger.error(cmdRet.get(RouteGlobal.InnerField.CmdResponseMsg));
			code = Globals.DeviceToCode.CommandsDevice_NONETTRUE;
			break;
		}
		// 请求超时
		case RouteGlobal.ResponseCode.TIME_OUT: {
			logger.error(cmdRet.get(RouteGlobal.InnerField.CmdResponseMsg));
			code = Globals.DeviceToCode.CommandsTime_out;
			break;
		}
		}
		return code;
	}

	/**
	 * @Title: cmdDescCode 
	 * @Description: 把错误码转换成国际化key，与途强对应
	 * @param staus
	 * @return 
	 * @author li.shangzhi
	 * @date 2017年7月27日 下午4:54:26
	 */
	public static String cmdDescCode(int staus) {
		switch (staus) {
		case 103:
			return "Commands.Not_Exist";
		case 255:
			return "Commands.Command_execution_success";
		case 240:
			return "Commands.Data_error";// 数据错误
		case 243:
			return "Commands.Not_support"; // 不支持
		case 252:
			return "Commands.Device_Busy";// 设备忙
		case 238:
			return "Commands.Device_interrupts";// 设备中断
		case 225:
			return "Commands.Time_out";// 超时！
		case 226:
			return "Commands.Parameter_error";// 参数错误
		case 227:
			return "Commands.Command_was_not_executed_correctly";// 指令未正确执行
		case 228:
			return "Commands.Device_NONET";// 与设备连接错误/设备不在线
		default:
			return "Commands.Unknown_error";// 未知错误
		}
	}
	
	/**
	 * @Title: cmdDescMsg 
	 * @Description: 将日志国际化key转换成具体值
	 * @param code
	 * @return 
	 * @author li.shangzhi
	 * @date 2017年7月27日 下午4:55:11
	 */
	public static String cmdDescMsg(String code) {
		switch (code) {
		case "Commands.Not_Exist":
		case "Commands.Command_execution_success":
		case "Commands.Data_error":
		case "Commands.Not_support":
		case "Commands.Device_Busy":
		case "Commands.Device_interrupts":
		case "Commands.Time_out":
		case "Commands.Parameter_error":
		case "Commands.Command_was_not_executed_correctly":
		case "Commands.Device_NONET":
		case "Commands.Unknown_error":
			return MessageSourceUtil.getMessage(code);
		default:
			return code;// 未知错误
		}
	}
	
	/**
	 * @Title: maintanceCmd 
	 * @Description: 解析指令模板，填充指令参数
	 * @param code
	 * @param params
	 * @return 
	 * @author li.shangzhi
	 * @date 2017年7月28日 下午8:48:38
	 */
	public static String maintanceCmd(String code,String[] params){
		if("{0}".equals(code)){
			MessageFormat format = new MessageFormat(code);
			return format.format(params);
		}
		// 先匹配格式，得到匹配后的
		MessageFormat format = new MessageFormat(code);
		String cmdMsg = format.format(params);
		//SOS号码添加,工作模式,白名单（GT300）三个指令的空值参数也保留位置
		if (code.indexOf("SOS,A,") != -1 || code.indexOf("MODE,1,") != -1 || code.indexOf("MODE,2,") != -1 || code.indexOf("WN,A,") != -1) {
			Matcher m = Pattern.compile("\\{[0-9]+\\}").matcher(cmdMsg);
			cmdMsg = m.replaceAll("");
		} else {
			Matcher m = Pattern.compile("[,|&\\]]+\\{[0-9]+\\}").matcher(cmdMsg);
			cmdMsg = m.replaceAll("");
		}
		return cmdMsg;
	}
}
